#!/usr/bin/env python
#-------------------------------------------------------------------------------------------------------
# Copyright (C) Microsoft. All rights reserved.
# Licensed under the MIT license. See LICENSE.txt file in the project root for full license information.
#-------------------------------------------------------------------------------------------------------

#
# TODO build.sh stopped using this script, is it needed?
#

from __future__ import print_function
import hashlib
import os
import shutil
import sys
import tarfile
import urllib
import urllib2
from zipfile import ZipFile
from argparse import ArgumentParser

# Parse Makefile.in to find the OBJECTS = ... list of object files
# This is the officially recommended way of integrating ICU into a large project's build system
def get_sources(icuroot, mkin_path):
    # ignore these files, similar to Node
    ignore = [
        "source/tools/toolutil/udbgutil.cpp",
        "source/tools/toolutil/dbgutil.cpp",
    ]
    ignore = [os.path.join(icuroot, os.path.normpath(source)) for source in ignore]

    def get_source(object_path):
        base = os.path.splitext(object_path)[0]
        cpp = base + ".cpp"
        c = base + ".c"

        # return None if we find a source but explicitly exclude it, compared
        # to raising an exception if a source is referenced that doesn't exist,
        # since that is more likely to be an issue with the source/ folder
        if cpp in ignore or c in ignore:
            return None

        if os.path.isfile(cpp):
            return cpp
        elif os.path.isfile(c):
            return c

        raise Exception("%s has no corresponding source file" % object_path)

    with open(mkin_path, "r") as mkin_contents:
        in_objs = False
        sources = []

        for line in mkin_contents:
            line = line.strip()

            if line[:7] == "OBJECTS":
                in_objs = True
                # trim " = " in "OBJECTS = {object files}"
                line = line[10:]

            elif in_objs == True and len(line) == 0:
                # done with OBJECTS, return
                return sources

            if in_objs == True:
                # trim " \" in "{object files} \"
                linelen = len(line)
                line = line[:linelen - 1].strip() if line[linelen - 1] == "\\" else line

                objects = map(lambda o: os.path.join(os.path.dirname(mkin_path), o), line.split())
                cpps = map(get_source, objects)
                cpps = filter(lambda cpp: cpp != None, cpps)
                sources.extend(cpps)

        # should have returned by now
        raise Exception("Could not extract sources from %s" % mkin_path)

def get_headers(icuroot, headers_path):
    # ignore these files, similar to Node
    ignore = [
        "source/tools/toolutil/udbgutil.h",
        "source/tools/toolutil/dbgutil.h",
    ]
    ignore = [os.path.join(icuroot, os.path.normpath(source)) for source in ignore]

    if not os.path.isdir(headers_path):
        raise Exception("%s is not a valid headers path" % headers_path)

    headers = map(lambda h: os.path.join(headers_path, h), os.listdir(headers_path))
    headers = filter(lambda h: os.path.splitext(h)[1] == ".h", headers) # only include .h files
    headers = filter(lambda h: h not in ignore, headers) # don't include ignored headers
    return headers

def create_msvc_props(chakra_icu_root, icu_sources_root, version):
    prelude = """<?xml version="1.0" encoding="utf-8"?>
<!-- DO NOT EDIT THIS FILE. It is auto-generated by $ChakraCore/tools/icu/%s -->
<Project DefaultTargets="Build" ToolsVersion="12.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <PropertyGroup>""" % os.path.basename(__file__)

    prop_template = """
    <Icu{0}{1}>
      {2}
    </Icu{0}{1}>"""

    conclusion = """
  </PropertyGroup>
</Project>
"""
    joiner = ";\n      "

    def add_props(propfile, deproot, dep):
        sources = get_sources(icu_sources_root, os.path.join(deproot, dep, "Makefile.in"))
        sources_prop = prop_template.format(dep.capitalize(), "Sources", joiner.join(sources))
        propfile.write(sources_prop)

        headers = get_headers(icu_sources_root, os.path.join(deproot, dep))
        headers_prop = prop_template.format(dep.capitalize(), "Headers", joiner.join(headers))
        propfile.write(headers_prop)

    with open(os.path.join(chakra_icu_root, "Chakra.ICU.props"), "w") as propfile:
        propfile.write(prelude)

        # Write all of the sources and header files to Icu${Dep}${DepKind}, such as IcuCommonSources
        sourceroot = os.path.join(icu_sources_root, "source")
        for dep in ["common", "i18n", "stubdata"]:
            add_props(propfile, sourceroot, dep)

        # tools are handled somewhat differently, since theyre in source/tools
        toolsroot = os.path.join(sourceroot, "tools")
        for dep in ["toolutil", "genccode"]:
            add_props(propfile, toolsroot, dep)

        version_parts = version.split(".")
        no_newline_prop_template = "\n    <Icu{0}>{1}</Icu{0}>"
        propfile.write(no_newline_prop_template.format("VersionMajor", version_parts[0]))
        propfile.write(no_newline_prop_template.format("VersionMinor", version_parts[1] if len(version_parts) > 1 else "0"))

        propfile.write(no_newline_prop_template.format("SourceDirectory", sourceroot))

        include_dirs = [os.path.join(sourceroot, component) for component in ["common", "i18n"]]
        propfile.write(prop_template.format("Include", "Directories", joiner.join(include_dirs)))

        propfile.write(conclusion)

def download_icu(icuroot, version, yes):
    # download either the zip or tar, depending on the os
    extension = "zip" if os.name == "nt" else "tgz"

    archive_file = "icu4c-{0}-src.{1}".format(version.replace(".", "_"), extension)
    # Use this in future, currently the SHA hash file name for version 63.2 seems to not include version name.
    #hash_file = "icu4c-{0}-src.{1}.asc".format(version.replace(".", "_"), extension)
    hash_file = "icu4c-SHASUM512.txt.asc"

    archive_url = "https://github.com/unicode-org/icu/releases/download/release-{0}/{1}".format(version.replace(".", "-"), archive_file)
    hash_url = "https://github.com/unicode-org/icu/releases/download/release-{0}/{1}".format(version.replace(".", "-"), hash_file)
    #print(archive_file)
    #print(hash_file)
    #print(archive_url)
    #print(hash_url)

    license_confirmation = """
{1}
This script downloads ICU from {0}.
It is licensed to you by its publisher, not Microsoft.
Microsoft is not responsible for the software.
Your installation and use of ICU is subject to the publisher's terms,
which are available here: http://www.unicode.org/copyright.html#License
{1}
""".format(archive_url, "-" * 80)
    if not yes:
        print(license_confirmation)
        response = raw_input("Do you agree to these terms? [Y/n] ")
        if response != "" and response != "y" and response != "Y":
            sys.exit(0)

    print("Downloading ICU from %s" % archive_url)

    archive_path = urllib.urlretrieve(archive_url)[0]

    print("Downloaded ICU to %s" % archive_path)

    # check the hash of the download zipfile/tarball
    checksum = ""
    with open(archive_path, "rb") as download:
        hashAlgorithm = hashlib.sha512()
        hashAlgorithm.update(download.read())
        checksum = hashAlgorithm.hexdigest()

    hash_path = os.path.join(icuroot, hash_file)
    hash_request = urllib2.urlopen(hash_url)
    allHashes = hash_request.read().decode("ascii").split("\n")
    relevant_hash = filter(lambda line: line[len(line) - len(archive_file):] == archive_file, allHashes)
    if len(relevant_hash) != 1:
        raise Exception("Could not find hash for {0} in {1}".format(archive_file, hash_url))

    correct_hash = relevant_hash[0]
    correct_hash = correct_hash.split(" ")[0]
    if (correct_hash == checksum):
        print("Hash checksums match, continuing")
        return archive_path
    else:
        raise Exception("Hash checksums do not match. Expected {0}, got {1}".format(correct_hash, checksum))

def extract_icu(icuroot, archive_path):
    tempdir = os.path.normpath(os.path.join(icuroot, "temp"))
    print("Extracting ICU to %s" % tempdir)
    opener = ZipFile if os.name == "nt" else tarfile.open
    with opener(archive_path, "r") as archive:
        archive.extractall(tempdir)

    icu_folder = os.path.join(icuroot, "icu")
    if os.path.isdir(icu_folder):
        shutil.rmtree(icu_folder)

    print("Extraction successful, ICU will be located at %s" % icu_folder)
    shutil.move(os.path.join(tempdir, "icu"), icu_folder)
    shutil.rmtree(tempdir)

def main():
    chakra_icu_root = os.path.normpath(os.path.join(os.path.realpath(__file__), "..", "..", "..", "deps", "Chakra.ICU"))

    argparser = ArgumentParser(description = "Download and set up ICU for use in ChakraCore")
    argparser.add_argument("-y", "--yes", action = "store_true", help = "Skip ICU License prompt text")
    argparser.add_argument("version", help = "ICU version to download. Not compatible with --archive", default = "61.1", nargs = "?")
    argparser.add_argument("-i", "--icu-root",
        help = "Path to directory to extract ICU to. Resulting directory will contain a single subfolder, 'icu', which contains ICU's source tree",
        default = chakra_icu_root
    )
    argparser.add_argument("-a", "--archive", help = "Path to icu.zip (Windows) or icu.tar (POSIX) that you have already downloaded")
    args = argparser.parse_args()

    archive_path = args.archive
    if args.version is not None and args.archive is None:
        archive_path = download_icu(args.icu_root, args.version, args.yes)

    extract_icu(args.icu_root, archive_path)
    if os.name == "nt":
        create_msvc_props(chakra_icu_root, os.path.join(args.icu_root, "icu"), args.version)

if __name__ == "__main__":
    main()
